package agent_test

import (
	"context"
	"path/filepath"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	core "k8s.io/api/core/v1"

	"github.com/datawire/dlib/dlog"
	rpc "github.com/telepresenceio/telepresence/rpc/v2/manager"
	"github.com/telepresenceio/telepresence/v2/cmd/traffic/cmd/agent"
	"github.com/telepresenceio/telepresence/v2/pkg/agentconfig"
	"github.com/telepresenceio/telepresence/v2/pkg/forwarder"
	"github.com/telepresenceio/telepresence/v2/pkg/tunnel"
)

const (
	appHost        = "appHost"
	appPort uint16 = 5000
)

func makeFS(t *testing.T, ctx context.Context) (forwarder.Interceptor, agent.State) {
	f := forwarder.NewInterceptor(agentconfig.PortAndProto{Proto: core.ProtocolTCP, Port: 1111}, tunnel.AgentToProxied, appHost, appPort)
	go func() {
		if err := f.Serve(context.Background(), nil); err != nil {
			dlog.Error(ctx, err)
		}
	}()

	assert.Eventually(t, func() bool {
		_, port := f.Target()
		return port == appPort
	}, 1*time.Second, 10*time.Millisecond)

	c, err := agent.LoadConfig(ctx)
	require.NoError(t, err)
	s := agent.NewState(c)
	cn := c.AgentConfig().Containers[0]
	cnMountPoint := filepath.Join(agentconfig.ExportsMountPoint, filepath.Base(cn.MountPoint))
	s.AddContainerState(cn.Name, agent.NewContainerState(s, cn, cnMountPoint, map[string]string{}))
	s.AddInterceptState(s.NewInterceptState(f, agent.NewInterceptTarget(cn.Intercepts), cn.Name))
	return f, s
}

func TestState_HandleIntercepts(t *testing.T) {
	ctx := testContext(t, nil)
	a := assert.New(t)
	f, s := makeFS(t, ctx)

	var (
		host    string
		port    uint16
		cepts   []*rpc.InterceptInfo
		reviews []*rpc.ReviewInterceptRequest
	)

	// Setup worked

	host, port = f.Target()
	a.Equal(appHost, host)
	a.Equal(appPort, port)

	// Handle resets state on an empty intercept list

	reviews = s.HandleIntercepts(ctx, cepts)
	a.Len(reviews, 0)
	a.Equal("", f.InterceptId())

	// Prepare some intercepts..

	cepts = []*rpc.InterceptInfo{
		{
			Spec: &rpc.InterceptSpec{
				Name:           "cept1Name",
				Client:         "user@host1",
				Agent:          "agentName",
				Mechanism:      "tcp",
				Namespace:      namespace,
				ServiceName:    serviceName,
				PortIdentifier: "http",
				ContainerPort:  8080,
				Protocol:       string(core.ProtocolTCP),
				TargetPort:     8080,
			},
			Id: "intercept-01",
		},
		{
			Spec: &rpc.InterceptSpec{
				Name:           "cept2Name",
				Client:         "user@host2",
				Agent:          "agentName",
				Mechanism:      "tcp",
				Namespace:      namespace,
				ServiceName:    serviceName,
				PortIdentifier: "http",
				ContainerPort:  8080,
				Protocol:       string(core.ProtocolTCP),
				TargetPort:     8080,
			},
			Id: "intercept-02",
		},
	}

	// Handle ignores non-active and non-waiting intercepts

	cepts[0].Disposition = rpc.InterceptDispositionType_NO_PORTS
	cepts[1].Disposition = rpc.InterceptDispositionType_NO_CLIENT

	reviews = s.HandleIntercepts(ctx, cepts)
	a.Len(reviews, 0)
	a.Equal("", f.InterceptId())

	// Handle reviews waiting intercepts

	cepts[0].Disposition = rpc.InterceptDispositionType_WAITING
	cepts[1].Disposition = rpc.InterceptDispositionType_WAITING

	reviews = s.HandleIntercepts(ctx, cepts)
	require.Len(t, reviews, 2)
	a.Equal("", f.InterceptId())

	// Reviews are in the correct order

	a.Equal(cepts[0].Id, reviews[0].Id)
	a.Equal(cepts[1].Id, reviews[1].Id)

	// First cept was accepted, second was rejected

	a.Equal(rpc.InterceptDispositionType_ACTIVE, reviews[0].Disposition)
	a.Equal(rpc.InterceptDispositionType_AGENT_ERROR, reviews[1].Disposition)
	a.Equal("Conflicts with the currently-waiting-to-be-served intercept \"intercept-01\"", reviews[1].Message)

	// Handle conflicts

	cepts[0].Disposition = rpc.InterceptDispositionType_ACTIVE
	cepts[1].Disposition = rpc.InterceptDispositionType_WAITING

	reviews = s.HandleIntercepts(ctx, cepts)
	a.Len(reviews, 1)
	a.Equal(cepts[1].Id, reviews[0].Id)

	a.Equal(rpc.InterceptDispositionType_AGENT_ERROR, reviews[0].Disposition)
	a.Equal("Conflicts with the currently-served intercept \"intercept-01\"", reviews[0].Message)

	// Handle resets state on an empty intercept list again

	reviews = s.HandleIntercepts(ctx, nil)
	a.Len(reviews, 0)
	a.Equal("", f.InterceptId())
}
